package cn.monkey.family.server.domain.uc.auth.rbac;

import cn.monkey.family.data.http.HttpHeaderConstants;
import cn.monkey.family.data.uc.auth.UserSession;
import cn.monkey.family.data.uc.org.Organization;
import cn.monkey.family.data.uc.resource.Resource;
import cn.monkey.family.data.uc.resource.ResourceType;
import cn.monkey.family.data.uc.role.RoleType;
import cn.monkey.family.data.uc.role.RoleVo;
import cn.monkey.family.rbac.dsl.http.HttpRequestPredicate;
import cn.monkey.family.rbac.dsl.http.HttpRequestPredicateDefinition;
import cn.monkey.family.rbac.dsl.http.HttpRequestPredicateFactory;
import cn.monkey.family.rbac.dsl.http.jakarta.JakartaHttpServletRequestPredicate;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.HttpClientErrorException;

import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;

public class RBACFilter implements Filter {

    protected final HttpRequestPredicateFactory<HttpServletRequest> httpRequestPredicateFactory;

    protected final HttpRequestPredicate<HttpServletRequest> ignoredRequestPredicate;

    public RBACFilter(HttpRequestPredicateFactory<HttpServletRequest> httpRequestPredicateFactory,
                      HttpRequestPredicateDefinition[] ignoredRequestDefinitions) {
        this.httpRequestPredicateFactory = httpRequestPredicateFactory;
        this.ignoredRequestPredicate = loadIgnoredRequestPredicate(httpRequestPredicateFactory, ignoredRequestDefinitions);
    }

    private HttpRequestPredicate<HttpServletRequest> loadIgnoredRequestPredicate(HttpRequestPredicateFactory<HttpServletRequest> httpRequestPredicateFactory,
                                                                                 HttpRequestPredicateDefinition[] ignoredRequestDefinitions) {
        return ignoredRequestDefinitions == null ? request -> HttpRequestPredicate.PredicateResult.MATCH :
                Stream.of(ignoredRequestDefinitions)
                        .map(httpRequestPredicateFactory::create)
                        .reduce(HttpRequestPredicate::or)
                        .orElse(r -> HttpRequestPredicate.PredicateResult.MATCH);
    }


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (servletRequest instanceof HttpServletRequest httpServletRequest &&
                servletResponse instanceof HttpServletResponse httpServletResponse) {
            this.doFilter0(httpServletRequest, httpServletResponse, filterChain);
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }


    private void doFilter0(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        UserSession userSession = (UserSession) httpServletRequest.getAttribute(UserSession.KEY);
        if (userSession == null) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        if (this.ignoredRequestPredicate.match(httpServletRequest) == HttpRequestPredicate.PredicateResult.MATCH) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        Map<String, Collection<RoleVo>> orgRoles = userSession.getOrgRoles();
        if (CollectionUtils.isEmpty(orgRoles)) {
            throw HttpClientErrorException.create(HttpStatus.UNAUTHORIZED, "EMPTY_ORG_ROLES", null, null, null);
        }
        Collection<RoleVo> roleVos = orgRoles.get(httpServletRequest.getHeader(HttpHeaderConstants.ORG_ID_KEY));
        roleVos = roleVos == null ? new ArrayList<>() : new ArrayList<>(roleVos);
        roleVos.addAll(orgRoles.get(Organization.EMPTY_ID));
        if (CollectionUtils.isEmpty(roleVos)) {
            throw HttpClientErrorException.create(HttpStatus.UNAUTHORIZED, "EMPTY_ROLES", null, null, null);
        }
        // admin
        if (roleVos.stream().anyMatch(role -> RoleType.ADMIN.getKey().equals(role.getType()))) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        HttpRequestPredicate<HttpServletRequest> httpServletRequestHttpRequestPredicate = Optional.ofNullable(this.getRequestPredicate(httpServletRequest))
                .orElse(roleVos.stream()
                        .map(RoleVo::getResources)
                        .filter(c -> !CollectionUtils.isEmpty(c))
                        .flatMap(Collection::stream)
                        .filter(resource -> ResourceType.HTTP.getCode().equals(resource.getType()))
                        .map(this::toHttpRequestPredicateDefinition)
                        .map(this.httpRequestPredicateFactory::create)
                        .reduce(HttpRequestPredicate::or)
                        .orElse(r -> HttpRequestPredicate.PredicateResult.NO_MATCH));
        httpServletRequest.setAttribute(JakartaHttpServletRequestPredicate.KEY, httpServletRequestHttpRequestPredicate);
        if (httpServletRequestHttpRequestPredicate.match(httpServletRequest) == HttpRequestPredicate.PredicateResult.MATCH) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        throw HttpClientErrorException.create(HttpStatus.UNAUTHORIZED, "NOT_ALLOWED", null, null, null);
    }

    @SuppressWarnings("unchecked")
    protected HttpRequestPredicate<HttpServletRequest> getRequestPredicate(HttpServletRequest httpServletRequest) {
        return (HttpRequestPredicate<HttpServletRequest>) httpServletRequest.getAttribute(JakartaHttpServletRequestPredicate.KEY);
    }

    protected HttpRequestPredicateDefinition toHttpRequestPredicateDefinition(Resource r) {
        HttpRequestPredicateDefinition httpRequestPredicateDefinition = new HttpRequestPredicateDefinition();
        httpRequestPredicateDefinition.setPath(r.getPath());
        httpRequestPredicateDefinition.setMethod(r.getMethod());
        Map<String, String> headers = new HashMap<>();
        headers.put(HttpHeaderConstants.PLATFORM_ID_KEY, r.getPlatform());
        httpRequestPredicateDefinition.setHeaders(headers);
        return httpRequestPredicateDefinition;
    }
}
